use std::collections::HashSet;
use tokio::sync::mpsc;
use tracing::{debug, info, warn};

use bc_base::{BlockId, block::Block, transaction::Transaction};
use bc_p2p::network_api::{P2PEventForApp, P2pNetWorkHandlerForApp};
use bc_p2p::{
    BC_FLOOD_TOPIC_NAME_PREFIX, BcP2PNetwork, ContentResponse, Multiaddr, P2pNetWorkError, PeerId,
    ProvidingContentId, ResponseChannel,
};

#[derive(thiserror::Error, Debug)]
pub enum BcLibNetWorkError {
    #[error("{0}")]
    P2pNetWorkError(#[from] P2pNetWorkError),

    #[error("{0}")]
    BlockError(#[from] bc_base::block::BlockError),
}

#[derive(Debug)]
pub struct BcNetworkManager {
    p2p_network: BcP2PNetwork,
    event_receiver_for_app: Option<mpsc::Receiver<P2PEventForApp>>,
    network_handler_for_app: P2pNetWorkHandlerForApp,
    topic_name_block: String,
    topic_name_tx: String,
}

impl BcNetworkManager {
    pub async fn new(
        secret_key_seed: Option<u8>,
        listen_address: Option<Multiaddr>,
        shutdown_signal: tokio::sync::watch::Receiver<bool>,
    ) -> Self {
        let (mut network, event_receiver_for_app) =
            BcP2PNetwork::new_and_start(secret_key_seed, listen_address, shutdown_signal)
                .await
                .unwrap();
        info!("BC P2P Network created and started.");

        let network_handler_for_app = network.take_network_handler_for_app().unwrap();
        let topic_name_block = format!("{}-block", BC_FLOOD_TOPIC_NAME_PREFIX);
        let topic_name_tx = format!("{}-tx", BC_FLOOD_TOPIC_NAME_PREFIX);

        Self {
            p2p_network: network,
            event_receiver_for_app: Some(event_receiver_for_app),
            network_handler_for_app,
            topic_name_block,
            topic_name_tx,
        }
    }

    pub async fn shutdown(&mut self) {
        self.p2p_network.shutdown().await;
    }

    pub fn take_event_receiver_for_app(&mut self) -> mpsc::Receiver<P2PEventForApp> {
        self.event_receiver_for_app.take().unwrap()
    }

    pub fn is_block_topic(&self, topic_name: &str) -> bool {
        topic_name == self.topic_name_block
    }

    pub fn is_tx_topic(&self, topic_name: &str) -> bool {
        topic_name == self.topic_name_tx
    }
}

impl BcNetworkManager {
    pub async fn register_topic_of_block(&self) {
        self.network_handler_for_app
            .register_topic(&self.topic_name_block)
            .await
    }
    pub async fn register_topic_of_tx(&self) {
        self.network_handler_for_app
            .register_topic(&self.topic_name_tx)
            .await
    }

    pub async fn broadcast_block(&self, block: &Block) {
        let result = self
            .network_handler_for_app
            .broadcast(&self.topic_name_block, block.serialize())
            .await;
        match result {
            Ok(_) => {
                debug!("Broadcast block success.")
            }
            Err(err) => {
                warn!("Failed to broadcast block: {}", err);
            }
        }
    }

    pub async fn broadcast_tx(&self, tx: &Transaction) {
        let result = self
            .network_handler_for_app
            .broadcast(&self.topic_name_tx, tx.serialize())
            .await;
        match result {
            Ok(_) => {
                debug!("Broadcast tx success.")
            }
            Err(err) => {
                warn!("Failed to broadcast tx: {}", err);
            }
        }
    }

    pub async fn start_providing(&self, block_id: &BlockId) {
        let providing_content_id = ProvidingContentId::new(*block_id.as_bytes_ref());
        debug!(
            "Start providing block({}) use content id({}).",
            block_id, &providing_content_id
        );
        let _ = self
            .network_handler_for_app
            .start_providing(&providing_content_id)
            .await;
    }

    pub async fn get_providers(&self, block_id: &BlockId) -> HashSet<PeerId> {
        let providing_content_id = ProvidingContentId::new(*block_id.as_bytes_ref());
        debug!(
            "Get providers for block({}) as content id({}).",
            block_id, &providing_content_id
        );
        self.network_handler_for_app
            .get_providers(&providing_content_id)
            .await
    }

    pub async fn request_content(
        &self,
        peer: &PeerId,
        block_id: &BlockId,
    ) -> Result<Block, BcLibNetWorkError> {
        let providing_content_id = ProvidingContentId::new(*block_id.as_bytes_ref());
        info!("Request file {providing_content_id} from {peer}");
        let block_seq = self
            .network_handler_for_app
            .request_content(peer, &providing_content_id)
            .await?;
        let block = Block::deserialize(&block_seq)?;
        Ok(block)
    }

    pub async fn respond_content(
        &self,
        content: Result<Block, String>,
        channel: ResponseChannel<ContentResponse>,
    ) {
        let content = match content {
            Ok(block) => Ok(block.serialize()),
            Err(err) => Err(err),
        };

        self.network_handler_for_app
            .respond_content(content, channel)
            .await;
    }
}
